Contents

Usage

PM::Screen is the primary object in ProMotion and a subclass of UIViewController. It adds some abstraction to make your life easier while still allowing the full power of a UIViewController.

class HomeScreen < PM::Screen
  title "Home"
  tab_bar_item item: "home-screen-tab", title: "Home"
  status_bar :light

  def on_load
    # set up subviews here
    add MyCustomView, frame: [[ 50, 50 ], [ 100, 100 ]]
    set_nav_bar_button :right, title: "Next", action: :go_to_next
  end

  # custom method, triggered by tapping right nav bar button set above
  def go_to_next
    open NextScreen # opens in same navigation controller, assuming we're in one
  end

  def will_appear
    # just before the view appears
  end

  def on_appear
    # just after the view appears
  end

  def will_disappear
    # just before the view disappears
  end

  def on_disappear
    # just after the view disappears
  end

end

Lifecycle Methods

All lifecycle methods are optional, but provide hooks for you to do certain tasks. These usually coincide with Cocoa Touch lifecycle methods but have slightly different semantics.

In ProMotion, will_* methods usually fire before an action, and on_* methods fire just after.

on_init

Fires only once, after the screen has been instantiated and all provided properties set. A good place to do further initialization of instance variables or set your tab bar icon.

def on_init
  @my_car = "Dude, where's my car?"
  set_tab_bar_item item: :favorites
end

screen_setup

Primarily used for setting up reusable PM::Screen subclasses, such as PM::WebScreen. Not recommended for normal app screens. But if you are building a subclass for a gem, this is where you would do your additional setup.

class PM::LaserScreen < UILaserViewController
  include PM::ScreenModule

  def screen_setup
    self.laserView = set_up_view
  end
end

load_view

Used for creating your screen's root view. If you don't implement this method or if you do and you fail to create a view, ProMotion will create a blank one for you.

Only fires when the view property is accessed for the first time.

def load_view
  self.view = UIView.alloc.initWithFrame(UIScreen.mainScreen.bounds)
end

on_load

Fires once, when the root view of the screen is accessed for the first time. This is where you normally add and style your subviews.

Keep in mind this method doesn't necessarily fire right away. For example, if you're creating a tab bar with four screens, the three screens that are not active will not fire on_load until you tap their tab. That's because the view isn't accessed until that moment.

def on_load
  self.view.addSubview UILabel.new
  add UILabel.new, { background_color: UIColor.redColor }
  # or kick off MotionKit or RMQ view building here.
end

will_appear

Runs just before the screen appears. Often used to ensure the right information is displayed.

def will_appear
  @name.text = @user.name
end

will_present

Runs just before the screen is pushed onto the navigation controller. Usually fires immediately after will_appear, but only if it is the first time that the screen is added to the navigation controller.

Not used all that often, but can be useful in some cases.

def will_present
  # About to present
end

on_appear

Runs just after the screen has appeared and stopped animating. Sometimes used to kick off other animations or start playing a video.

def on_appear
  @video.startPlaying
end

on_present

Runs just after the screen is pushed onto the navigation controller. Usually fires just after on_appear, but only when the screen is first added to the navigation controller.

def on_present
  # Presented
end

will_disappear

Runs just before the screen disappears from the screen. An example usage would be to stop a video from playing.

def will_disappear
  @video.stopPlaying
end

will_dismiss

Runs just before the screen is removed from its parent. Usually happens when getting popped off a navigation controller stack. Fires right after will_disappear.

def will_dismiss
  # dismissing
end

on_dismiss

Runs just after the screen is removed from its parent.

def on_dismiss
  # dismissed, screen is about to be deallocated probably
end

should_autorotate

(iOS 6+) return true/false if screen should autorotate. Defaults to true.

def should_autorotate
  false
end

should_rotate(orientation)

(iOS 5) Return true/false for rotation to orientation. Tries to resolve this automatically from your UISupportedInterfaceOrientations setting. You normally do not override this method.

def should_rotate(orientation)
  if orientation == UIInterfaceOrientationPortrait
    true
  else
    false
  end
end

will_rotate(orientation, duration)

Runs just before the device is rotated.

def will_rotate(orientation, duration)
  # about to rotate
end

on_rotate

Runs just after the device is rotated.

def on_rotate
  # we've rotated
end

on_memory_warning

Runs when your screen receives a memory warning. This is an alias of UIKit's didReceiveMemoryWarning method on UIViewController that you don't need to call super on. So read Apple's documentation to see what you need to do here.

def on_memory_warning
  # Free up some memory here
  @my_var = nil
  # do NOT call super. It will be done for you.
end

Methods

app

Returns the UIApplication.sharedApplication

# Instead of
UIApplication.sharedApplication.someMethod
# Use
app.someMethod

app_delegate

Returns the UIApplication.sharedApplication.delegate

# Instead of
UIApplication.sharedApplication.delegate.someMethod
# Use
app_delegate.someMethod

app_window

Returns the current app_delegates UIWindow.

app_window.addSubview someView

try(method, *args)

Sends method(*args) to the current screen if the current screen will respond_to?(method)

Returns if the screen was opened in a modal window.

m = ModalScreen.new
open_modal m
m.modal? # => true

Returns if the screen is currently contained in a navigation controller.

screen = HomeScreen.new
open screen
screen.nav_bar? # => true

will_rotate(orientation, duration)

Runs just before the screen rotates.

set_nav_bar_button(side, args = {})

Set a nav bar button. args can be image:, title:, system_item:, button:, custom_view:.

You can also set arbitrary attributes in the hash and they'll be applied to the button.

set_nav_bar_button :left, {
  title: "Button Title",
  image: UIImage.imageNamed("left-nav"),
  system_item: :reply,
  tint_color: UIColor.blueColor,
  button: UIBarButtonItem.initWithTitle("My button", style: UIBarButtonItemStyleBordered, target: self, action: :tapped_button) # for custom button
}

system_item can be a UIBarButtonSystemItem or one of the following symbols:

:done,:cancel,:edit,:save,:add,:flexible_space,:fixed_space,:compose,
:reply,:action,:organize,:bookmarks,:search,:refresh,:stop,:camera,
:trash,:play,:pause,:rewind,:fast_forward,:undo,:redo,:page_curl

custom_view can be any custom UIView subclass you initialize yourself

Another example with arbitrary attributes:

set_nav_bar_button :right, {
  system_item: :add,
  action: :add_item,
  accessibility_label: "add item",
  background_color: UIImage.imageNamed("some-image")
}

And finally, you can also set the :back nav bar image on a screen and it will render a back arrow icon in the upper left part of the navigation. However, note that doing so will change the back button for all descendants of the screen you set the button on. This behavior is a little unintuitive, but is a result of the underlying Cocoa Touch APIs. For example:

class MyScreen < PM::Screen
  def on_init
    set_nav_bar_button :back, title: 'Cancel', style: :plain, action: :back
  end

  def go_to_next_screen
    open MyScreenChild
  end
end

The code above will add a "cancel" back button to MyScreenChild when it is opened as a descendant of MyScreen.

set_nav_bar_buttons(side, button_array)

Allows you to set multiple buttons on one side of the nav bar with a single method call. The second parameter should be an array of any mixture of UIBarButtonItem instances and hash constructors used in set_nav_bar_button

set_nav_bar_buttons :right, [{
  custom_view: my_custom_view_button
},{
  title: "Tasks",
  image: UIImage.imageNamed("whatever"),
  action: nil
}]

set_toolbar_items(buttons = [], animated = true)

Uses an array to set the navigation controllers toolbar items and shows the toolbar. Uses the same hash formatted parameters as set_nav_bar_button. When calling this method, the toolbar will automatically be shown (even if the screen was created without a toolbar). Use the animated parameter to specify if the toolbar showing should be animated or not.

# Will arrange one button on the left and another on the right
set_toolbar_items [{
    title: "Button Title",
    action: :some_action
  }, {
    system_item: :flexible_space
  }, {
    title: "Another ButtonTitle",
    action: :some_other_action,
    target: some_other_object
  }]

You can also pass your own initialized UIBarButtonItem as part of the array (instead of a hash object).

EDGE FEATURE: You can pass tint_color with a UIColor to change the tint color of the button item.

title

Returns title of current screen.

class MyScreen < PM::Screen
  title "Mine"
  def on_load
    self.title # => "Mine"
  end

title=(title)

Sets the text title of current screen instance. Note that you must declare self as the receiver.

class SomeScreen
  def on_load
    # This sets this instance's title
    self.title = "Something else"
  end
end

app_delegate

Returns the AppDelegate. Alias of UIApplication.sharedApplication.delegate.

close(args = {})

Closes the current screen, passes args back to the previous screen's on_return method (if it exists).

class ChildScreen < PM::Screen
  def save_and_close
    save
    close({ saved: true })
  end
end

class ParentScreen < PM::Screen
  def on_return(args={})
    if args[:saved]
      reload_data
    end
  end
end

If you want to close back to the root screen or any other screen in the navigation stack, use to_screen::

class ChildScreen < PM::Screen
  def close_to_root
    close to_screen: self.navigation_controller.viewControllers.first
    # For the rootViewController, you can just use `:root`
    close to_screen: :root
  end
end

open_root_screen(screen)

Closes all other open screens and opens screen as the root view controller.

def reset_this_app
  open_root_screen HomeScreen
end

open(screen, args = {})

Opens a screen, intelligently determining the context.

Examples:

# In app delegate
open HomeScreen     # class or
open HomeScreen.new # instance

# In tab bar
open HomeScreen, hide_tab_bar: true

# `modal: true` is the same as `open_modal`.
open ModalScreen, modal: true, animated: true

# Opening a modal screen with transition or presentation styles
open_modal ModalScreen.new(nav_bar: true,
    transition_style: UIModalTransitionStyleFlipHorizontal,
    presentation_style: UIModalPresentationFormSheet)

# From any screen (same as `open_root_screen`)
open HomeScreen, close_all: true

# Opening a screen in a different tab or split view screen
open DetailScreen.new, in_tab: "Tab name" # if you're in a tab bar
open MasterScreen, in_master: true # if you're in a split view (opened in current navigation controller if not)
open DetailScreen, in_detail: true # if you're in a split view (opened in current navigation controller if not)

# Opening a screen with a custom navigation_controller class. (Defaults to PM::NavigationController)
class MyNavigationController < PM::NavigationController; end
open HomeScreen.new(nav_bar: true, nav_controller: MyNavigationController), close_all: true

# Opens a screen with a navigation controller but with the navigation bar hidden
open HomeScreen.new(nav_bar: true, hide_nav_bar: true) # Edge feature
Setting screen accessors

Any writable attribute (accessor, setter methods etc.) in screen can also be set in the new hash argument.

class HomeScreen < PM::Screen
  def profile_button_tapped
    open ProfileScreen.new(user: @current_user)
  end
end

class ProfileScreen < PM::Screen
  attr_accessor :user

  def on_load
    puts user # => @current_user object
  end
end

open_modal(screen, args = {})

Opens a modal screen. Same as open HomeScreen, modal: true

supported_orientation?(orientation)

Returns whether UISupportedInterfaceOrientations includes the given orientation.

supported_orientation?(UIInterfaceOrientationMaskPortrait)
supported_orientation?(UIInterfaceOrientationMaskLandscapeLeft)
supported_orientation?(UIInterfaceOrientationMaskLandscapeRight)
supported_orientation?(UIInterfaceOrientationMaskPortraitUpsideDown)

supported_orientations

Returns the value for UISupportedInterfaceOrientations.

supported_device_families

Returns either :iphone or :ipad. Should probably be named current_device_family or something.

first_screen?

Boolean representing if this is the first screen in a navigation controller stack.

def on_appear
  self.first_screen? # => true | false
end

add_child_screen(screen)

Adds a screen as a childViewController. Usually used if you plan to add the child screen's primary view as a subview. Returns the child screen instance. Will also instantiate the instance if given a class.

def on_load
  @child = add_child_screen MyLittleScreen
  add @child.view, { frame: [[ 100, 100 ], [ 100, 50 ]] }
end

The parent screen will be accessible from the child as parent_screen.

remove_child_screen(screen)

Removes a child screen from the current screen.

def foo
  remove_child_screen @child
end

Class Methods

title(new_title)

Sets the default text title for all of the instances of this screen.

class MyScreen < PM::Screen
  title "Some screen"
  # ...
end

title_view(new_title_view)

Sets an arbitrary view as the nav bar title.

class MyScreen < PM::Screen
  title_view UILabel.new
  # ...
end

title_image(new_image)

Sets an arbitrary image as the nav bar title.

class MyScreen < PM::Screen
  title_image "image.png"
  # ...
end

status_bar(style=nil, args={animation: UIStatusBarAnimationSlide})

Set the properties of the application's status bar. Options for style are: :none, :light, :dark, and :default. If a screen doesn't call status_bar and a UIStatusBarStyle is set on the application bundle, then that style will be used. Otherwise, UIStatusBarStyleDefault will be used. The animation argument should be a UIStatusBarAnimation (or :none / :fade / :slide) and is used to hide or show the status bar when appropriate and defaults to :slide. If status_bar is set to false in the app delegate this will default to hidden as well.

class MyScreen < PM::Screen
  status_bar :none, {animation: :fade}
  # ...
end

class MyScreenWithADarkColoredNavBar < PM::Screen
  status_bar :light
  # ...
end

Add a navigation bar (and UINavigationController wrapper) to the current screen whenever it's opened. You can specify a custom transition for when opened in a modal, toggle the bottom toolbar, and even specify a custom UINavigationController subclass if you want (default is PM::NavigationController).

# Typical
class MyScreen < PM::Screen
  nav_bar true
end

# Expanded options
class MyScreen < PM::Screen
  nav_bar true, {
    nav_controller: PM::NavigationController,
    toolbar: true,
    transition_style: UIModalTransitionStyleCrossDissolve,
    presentation_style: UIModalPresentationFormSheet,
  }
end

Creates a nav bar button in the specified position with the given options

class HomeScreen < PM::Screen
  nav_bar_button :left, title: "Back", style: :plain, action: :back
  # ...
end

bounds

Alias for self.view.bounds

frame

Alias for self.view.frame


Accessors

parent_screen

References the screen immediately before this one in a navigation controller or the presenting screen for modals. You should set this yourself if you're doing something funky like addChildViewController.

def on_appear
  self.parent_screen # => PM::Screen instance
end

view

The main view for this screen.